/**********************************************
*  Classes:   GameApp
*  Desc:      Singleton class. Runs game
*			  
*  Author:    Vaios Kalpias-Ilias (C) 2008-2009
***********************************************/


#include "GameApp.h"
#include "EventManager.h"
#include "Quaternion.h"
#include "Helper.h"
#include "Steering.h"
#include "SteeringWanderCircle.h"
#include "TextureProperty.h"
#include "MaterialProperty.h"
#include "GeometryNode.h"
#include "BillboardNode.h"
#include "CdnResourceLoader.h"
#include "CdnRenderer.h"
#include "ParticleSystemManager.h"
#include "ParticleSystemTest.h"
#include "ScriptManager.h"
#include <stdio.h>
#include "CameraMover.h"
#include <time.h>
#include "Callback.h"
#include "SkyBox.h"

using namespace mth;
using namespace AI;
using namespace boost;

// test globals
TransformNodePtr root;
int numOfShips = 0;
SteeringWanderCircle *wanderCircle;
Kinematic2d *ships;
Steering2d *shipSteer;
GeometryNodePtr *shipMeshes;
Kinematic2d target(Vector2(20.0f , -20.0f), Vector2(), 0.0f,0.0f);
LightPtr light,light2,light3;

//-----------------------------------------------------
GameApp* GameApp::m_pInstance = 0;
//-----------------------------------------------------
/*
* Ctor
*/
GameApp::GameApp() :
m_pInputMgr(0),
m_pEventMgr(0),
m_pScriptMgr(0),
m_pRenderer(0),
m_fDeltaTime(0.0f),
m_fcTimeStep(0.01f),
m_fcSpeedModifier(40.0f),
m_bPerfTimer(false),
m_PerfFreq(0),
m_fPerfFreq(0.0f),
m_NextTime(0),
m_fNextTime(0.0),
m_LastTime(0),
m_fLastTime(0.0f),
m_fTimeAccum(0.0f),
m_fcMaxDT(1.0f),
m_fTotalTime(0.0f),
m_bActive(true)
{

}
//-----------------------------------------------------
/*
* Dtor
*/
GameApp::~GameApp()
{
}
//-----------------------------------------------------
/*
* Gets single instance
*/
GameApp* GameApp::GetInstance()
{
	return m_pInstance ? m_pInstance :
		                 m_pInstance = new GameApp();
}
//-----------------------------------------------------
/*
* Destroys single instance
*/
void GameApp::Destroy()
{
	if (m_pInstance)
	{
		delete m_pInstance;
		m_pInstance = 0;
	}
}
//-----------------------------------------------------
/*
* Windows messages handler
*/
LRESULT CALLBACK GameApp::MsgHandler(HWND hwnd, 
						    UINT message, 
							WPARAM wparam, 
							LPARAM lparam)
{
	if (!m_pInstance) return 0;

	switch (message)									// Check For Windows Messages
	{
	case WM_ACTIVATE:							// Watch For Window Activate Message
		{
			if (!HIWORD(wparam))					// Check Minimization State
			{
				m_pInstance->m_bActive = true;			// Program Is Active

			}
			else
			{
				m_pInstance->m_bActive = false;		// Program Is Not Active

			}

			return 0;							
		}

	case WM_SYSCOMMAND:							// Intercept System Commands
		{
			switch (wparam)					    // Check System Calls
			{
			case SC_SCREENSAVE:					// Screensaver Trying To Start?
			case SC_MONITORPOWER:				// Monitor Trying To Enter Powersave?
				return 0;							// Prevent From Happening
			}
			break;							    // Exit
		}

	case WM_CLOSE:								// Did We Receive A Close Message?
		{
			m_pInstance->Quit();						    // Send A Quit Message
			return 0;							    // Jump Back
		}

	case WM_DESTROY:
		{
			PostQuitMessage(0);
			return 0;
		}

	case WM_KEYDOWN:							// Is A Key Being Held Down?
		{
			m_pInstance->m_pInputMgr->KeyDown((unsigned int)wparam);

			return 0;								// Jump Back
		}

	case WM_KEYUP:								// Has A Key Been Released?
		{
			m_pInstance->m_pInputMgr->KeyUp((unsigned int)wparam);
			return 0;								// Jump Back
		}

	case WM_SIZE:								// Resize The OpenGL Window
		{
			m_pInstance->m_pRenderer->ResizeWindow(LOWORD(lparam),HIWORD(lparam));  // LoWord=Width, HiWord=Height
			return 0;								// Jump Back
		}
	}

	return DefWindowProc(hwnd, message, wparam, lparam);
}

//-----------------------------------------------------
/*
* Start main windows loop
*/
int GameApp::Start(HINSTANCE &hInstance)
{
	
	MSG		msg;									// Windows Message Structure
	BOOL	done=FALSE;								// Bool Variable To Exit Loop

	srand((unsigned int)(time(0)));
	

	if (!Init(hInstance)) return 0;

	while(!done)									// Loop That Runs While done=FALSE
	{
		if (m_pRenderer)
		{
			if (PeekMessage(&msg, m_pRenderer->GetHwnd(),0,0,PM_REMOVE))	// Is There A Message Waiting?
			{
				if (msg.message==WM_QUIT)				// Have We Received A Quit Message?
				{
					done = true;							// If So done=TRUE
				}
				else									// If Not, Deal With Window Messages
				{
					TranslateMessage(&msg);				// Translate The Message
					DispatchMessage(&msg);				// Dispatch The Message
				}
			}
			else										// If There Are No Messages
			{

				// game step
				if (m_bActive)
				{
					if (!Run())
					{
						done = true;
					}			
					else									
					{
						SwapBuffers(m_pRenderer->GetHdc());	// Swap Buffers (Double Buffering)
					}	
				}

			}
		}
		else
		{
			done = true;
		}
		
	}

	// Shutdown
	ShutDown();
	
	return ((int)msg.wParam);		// Exit The Program
}
//-----------------------------------------------------
/*
* Init game application
*/
bool GameApp::Init(HINSTANCE &hInstance)
{
	//init renderer and window
	CdnRenderer::SetType(CdnRenderer::TYPE_OPENGL);
	m_pRenderer = CdnRenderer::GetInstance();
	if (!m_pRenderer)
	{
		return false;
	}
	else if (!m_pRenderer->Init(hInstance, false, 320, 240))
	{
		return false;
	}
	
	// init our timers
	InitializeTimers();

	// init our managers
	if (!InitializeManagers())
	{
		ShutDown();
		return false;
	}

	// setup our scene
	if (!SetupScene())
	{
		ShutDown();
		return false;
	}

	return true;
}
//-----------------------------------------------------
/*
* Initializes timers
*/
void GameApp::InitializeTimers()
{
	// is a performance counter available
	if (QueryPerformanceFrequency((LARGE_INTEGER *) &m_PerfFreq)) 
	{		
		m_bPerfTimer = true;       
		m_fPerfFreq = 1.0f / (float)m_PerfFreq;
		QueryPerformanceCounter((LARGE_INTEGER *) &m_LastTime);
		m_fLastTime = (float)m_LastTime*m_fPerfFreq;
		m_fNextTime = m_fLastTime;


	}
	else {

		// no performance counter, read in using timeGetTime
		m_LastTime = timeGetTime();
		m_fLastTime = (float)m_LastTime / 1000.0f;

	}
}
//-----------------------------------------------------
/*
* Initializes managers
*/
bool GameApp::InitializeManagers()
{
	// init input manager
	m_pInputMgr = InputManager::GetInstance();
	if (!m_pInputMgr)
	{
		return false;
	}

	m_pInputMgr->AssignFuncKeyUp(VK_ESCAPE, BindFunc(GameApp, Quit, this) );

	// init event manager
	m_pEventMgr = EventManager::GetInstance();
	if (!m_pEventMgr)
	{
		return false;
	}

	// init script manager
	m_pScriptMgr = ScriptManager::GetInstance();
	if (!m_pScriptMgr)
	{
		return false;
	}

	return true;
}
//-----------------------------------------------------
/*
* Sets up the scene
*/
bool GameApp::SetupScene()
{
	// create a camera mover to move our camera with user input
	CameraMover* cameraMover = new CameraMover();
	if (!cameraMover->Init())
	{
		delete cameraMover;
		return false;
	}

	// set the renderer's background color
	m_pRenderer->SetBackgroundColor(Vector3(0.0, 0.0, 0.0));

	// load our config script
	if (!m_pScriptMgr->
		Load("test", "assets/scripts/config.lua"))
	{
		return false;
	}

	// get necessary variables from config script
	m_pScriptMgr->ActivateScript("test");
	m_pScriptMgr->ExecuteActiveScript();
	std::string model = m_pScriptMgr->PopLuaStringFromTable("model", "data");
	std::string textureName = m_pScriptMgr->PopLuaStringFromTable("texture", "data");
	numOfShips = m_pScriptMgr->PopLuaIntFromTable("numOfObjects", "data");
	std::string skyTexture = m_pScriptMgr->PopLuaStringFromTable("skyTexture", "data");

	if (numOfShips <= 0)
	{
		return false;
	}

	// setup the rest of our scene (test code)
	wanderCircle = new SteeringWanderCircle[numOfShips];
	ships = new Kinematic2d[numOfShips];
	shipSteer = new Steering2d[numOfShips];
	shipMeshes = new GeometryNodePtr[numOfShips];

	root = TransformNodePtr(new TransformNode());
	CdnResourceLoader* pResourceLoader = CdnResourceLoader::GetInstance();
	const char* shipFilename = model.c_str();
	if (!pResourceLoader->LoadGeometryResource(shipFilename))
	{
		return false;
	}

	pResourceLoader->GetMultipleGeometryNodes(shipFilename,
											  numOfShips,
											  shipMeshes);
	float scale = 3.04f;
	
	TexturePropertyPtr texture = pResourceLoader->LoadTextureResource(textureName.c_str(), true);
	if (!texture)
	{
		return false;
	}
	
	// create a skybox
	SkyBoxPtr skyBox = SkyBoxPtr(new SkyBox());
	skyBox->SetTextures(pResourceLoader->LoadTextureResource(skyTexture.c_str()));
	skyBox->SetSize(250.0f);
	skyBox->SetMoveWithCamera(true);
	m_pRenderer->SetSkyBox(skyBox);

	MaterialPropertyPtr mat = MaterialPropertyPtr(new MaterialProperty());
	mat->SetDiffuse(Vector3(1.0, 1.0, 1.0));
	mat->SetSpecular(Vector3(1.0, 1.0, 1.0));
	mat->SetShininess(90.0f);

	light = LightPtr(new Light(Light::LIGHT_DIRECTIONAL));
	light->SetDiffuse(Vector3(1.0f, 0.5f, 0.5f));
	light->SetPosition(Vector3(0.0f, 0.0f, 100.0f));

	light2 = LightPtr(new Light(Light::LIGHT_DIRECTIONAL));
	light2->SetDiffuse(Vector3(0.5f, 1.0f, 0.5f));
	light2->SetPosition(Vector3(-100.0f, 50.0f, -100.0f));

	light3 = LightPtr(new Light(Light::LIGHT_DIRECTIONAL));
	light3->SetDiffuse(Vector3(0.5f, 0.5f, 1.0f));
	light3->SetPosition(Vector3(0.0f, 50.0f, 0.0f));

	root->AddProperty(mat);
	root->AddProperty(texture);
	root->AddLight(light);
	root->AddLight(light2);
	root->AddLight(light3);
	
	for (int i=0; i<numOfShips; i++)
	{
		shipMeshes[i]->SetScale(Vector3(scale, scale, scale));
		shipMeshes[i]->SetName(std::string("mesh") + std::string("%d",i));
		root->AttachChild(shipMeshes[i]);
	}

	CameraPtr camera = m_pRenderer->GetCamera();
	float cx = m_pScriptMgr->PopLuaFloatFromTable("cameraX", "data");
	float cy = m_pScriptMgr->PopLuaFloatFromTable("cameraY", "data");
	float cz = m_pScriptMgr->PopLuaFloatFromTable("cameraZ", "data");
	int   attachToObject = m_pScriptMgr->PopLuaFloatFromTable("cameraAttachedToObject", "data");
	float ctx = m_pScriptMgr->PopLuaFloatFromTable("cameraTargetPosX", "data");
	float cty = m_pScriptMgr->PopLuaFloatFromTable("cameraTargetPosY", "data");
	float ctz = m_pScriptMgr->PopLuaFloatFromTable("cameraTargetPosZ", "data");
	int   ctIndex = m_pScriptMgr->PopLuaFloatFromTable("cameraTargetObject", "data");

	camera->SetTranslation(Vector3(cx, cy, cz));
	if (ctIndex >= 0 && ctIndex < numOfShips)
	{
		camera->SetTarget(shipMeshes[ctIndex]);
	}
	else
	{
		camera->SetTarget(Vector3(ctx, cty, ctz));
	}

	if (attachToObject >= 0 && attachToObject < numOfShips)
	{
		shipMeshes[attachToObject]->AttachChild(camera);
	}

	camera->SetAlwaysLookForward(false);

	camera->SetName("camera");

	return true;
}
//-----------------------------------------------------
/*
* ShutDown game application
*/
void GameApp::ShutDown()
{

	if (wanderCircle)
	{
		delete [] wanderCircle;
		wanderCircle = 0;
	}
	if (ships)
	{
		delete [] ships;
		ships = 0;
	}

	if (shipSteer)
	{
		delete [] shipSteer;
		shipSteer = 0;
	}
	if (shipMeshes)
	{
		delete [] shipMeshes;
		shipMeshes = 0;
	}

	CdnResourceLoader::Destroy();
	InputManager::Destroy();
	m_pInputMgr = 0;
	EventManager::Destroy();
	m_pEventMgr = 0;
	ScriptManager::Destroy();
	m_pScriptMgr = 0;
	CdnRenderer::Destroy();
	m_pRenderer = 0;
	ParticleSystemManager::Destroy();
	
	root = TransformNodePtr();
}
//-----------------------------------------------------
/*
* The game update step
*/
bool GameApp::Run()
{
	// process input
	ProcessInput();
	// check for quit
	if (m_pInputMgr->WasQuitPressed())
	{
		return false;
	}

	// update time 
	UpdateTime();


	// perform AI
	DoAI();
	// update the entities using a fixed timestep
	while (m_fTimeAccum >= m_fcTimeStep)
     {
		// update game entities
		DoPhysics();
		// increase total time
		m_fTotalTime += m_fcTimeStep;
		// decrease time accumulator
        m_fTimeAccum -= m_fcTimeStep;		
     }
	 
	// render scene and UI
	UpdateFrame();
	RenderFrame();
	RenderScreenItems();
	
	
	return true;
}
//-----------------------------------------------------
/*
* Resets game
*/
void GameApp::Reset()
{
	m_fDeltaTime = 0.0f;
	m_bPerfTimer = false;
	m_PerfFreq = 0;
	m_NextTime = 0;
	m_LastTime = 0;
	m_fTimeAccum = 0.0f;
	m_fTotalTime = 0.0f;
	if (m_pInputMgr)
	{
		m_pInputMgr->Reset();
	}

	if (m_pEventMgr)
	{
		m_pEventMgr->Reset();
	}
	if (m_pScriptMgr)
	{
		m_pScriptMgr->Reset();
	}
}
//-----------------------------------------------------
/*
* Processes Input
*/
void GameApp::ProcessInput()
{
	m_pInputMgr->ProcessInput();
}
//-----------------------------------------------------
/*
* Executes AI
*/
void GameApp::DoAI()
{
	m_pEventMgr->DispatchEvents();


	for (int i=0; i<numOfShips; i++)
	{
		shipSteer[i] = wanderCircle[i].GetSteering(ships[i]);		
	}
	
}
//-----------------------------------------------------
/*
* Updates Time Step
*/
void GameApp::UpdateTime()
{
	// if there is a performance counter use it
	if (m_bPerfTimer)
	{
		QueryPerformanceCounter((LARGE_INTEGER *) &m_NextTime);
		m_fNextTime = m_NextTime* m_fPerfFreq;
	}
	else
	{
		m_NextTime = timeGetTime();
		m_fNextTime = (float)m_NextTime / 1000.0f;
	}

	// calculate and damp delta time
	m_fDeltaTime = (float)(m_fNextTime - m_fLastTime);
	if (m_fDeltaTime > m_fcMaxDT)
	{
		m_fDeltaTime = m_fcMaxDT;
	}
	// update last time and time accumulator
	m_LastTime = m_NextTime;
	m_fLastTime = m_fNextTime;
	m_fTimeAccum += m_fDeltaTime;

}
//-----------------------------------------------------
/*
* Performs physics step
*/
void GameApp::DoPhysics()
{
	for (int i=0; i<numOfShips; i++)
	{
		Steering::Update(ships[i], shipSteer[i], m_fcSpeedModifier * m_fcTimeStep);
	}	
}

//-----------------------------------------------------
/*
* Updates everything in the scene
*/
void GameApp::UpdateFrame()
{
	//static float rrr = 0.0f;
	//rrr -= 0.01f;
	for (int i=0; i<numOfShips; i++)
	{	
		shipMeshes[i]->SetTranslation(Vector3(ships[i].position.x,
			0.0f, -ships[i].position.y));
		
		//shipMeshes[i]->SetTranslation(Vector3(0.0, rrr, 0.0));
		
		Quaternion q1;
		q1.FromAxisAngle(0.0, 1.0, 0.0, ships[i].orientation);
		shipMeshes[i]->SetRotation(q1);
		
	}

	root->Update();	
}
//-----------------------------------------------------
/*
* Renders the scene
*/
void GameApp::RenderFrame()
{
	m_pRenderer->RenderScene(root.get());	
}
//-----------------------------------------------------
/*
* Renders UI
*/
void GameApp::RenderScreenItems()
{
	
}
//-----------------------------------------------------
/*
* Quits the application
*/
void GameApp::Quit()
{
	if (m_pInputMgr)
		m_pInputMgr->SetQuit(true);

}